package io.gitee.pkmer.core.base32;


import io.gitee.pkmer.util.NumUtil;
import io.gitee.pkmer.util.StrUtils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @author <a href="mailto:1193094618@qq.com">pkmer</a>
 * <br>
 * <a href = "https://gitee.com/pkmer/pkmer-toolkit">Code Repository</a>
 * At 2024/8/22
 * Base32加强版
 */
public class Base32Plus {
    // https://www.ietf.org/rfc/rfc4648.txt
    private static final int fiveBitUnit = 5;
    private static final String defaultEncodeTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    private static final int defaultCharNum = 8;
    private static final Character defaultPadding = '=';
    private final String encodeTable;
    private final Character padding;
    private final int charNum;

    public Base32Plus() {
        this(getBase32Alphabet(), defaultPadding, defaultCharNum);
    }

    public Base32Plus(String encodeTable, Character padding, int defaultCharNum) {
        this.encodeTable = encodeTable;
        this.padding = padding;
        this.charNum = defaultCharNum;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        private String encodeTable = defaultEncodeTable;
        private Character padding = defaultPadding;

        private int charNum = defaultCharNum;

        public Builder setEncodeTable(String encodeTable) {
            this.encodeTable = encodeTable;
            return this;
        }

        public Builder setPadding(Character c) {
            this.padding = c;
            return this;
        }

        public Builder setCharNum(int charNum) {
            this.charNum = charNum;
            return this;
        }

        public Base32Plus get() {
            return new Base32Plus(encodeTable, padding, charNum);
        }
    }


    /**
     * 实现RFC的Base32
     * https://www.ietf.org/rfc/rfc4648.txt
     *
     * @param msg 原始字符 ASCII
     * @return Base32编码
     */
    public String encode(String msg) {
        return encode(msg.getBytes(StandardCharsets.UTF_8));
    }


    public String encode(byte[] bytes) {
        int suffixFive = 0x1F;
        StringBuilder strContainer = new StringBuilder();
        int size = 0;
        int remain = 0;
        for (byte a : bytes) {
            // 转无符号整数
            // a &= 0xFF;
            int b = a & 0xFF;
            if (size == 0) {
                int i = b >>> 3 & suffixFive;
                strContainer.append(encodeTable.charAt(i));
                // 处理后三个bit
                size = 3;
                remain = b & 0b111;
            } else {
                int hight = remain << (5 - size);
                int low = b & 0xFF; // 确保 b 是无符号的
                int shifted = low >>> (8 - (5 - size)); // 无符号右移
                int i = hight | shifted;
                strContainer.append(encodeTable.charAt(i));

                // update
                size = (8 - (5 - size));
                remain = b & ((1 << size) - 1);

                while (size >= 5) {
                    int t = (remain >>> (size - 5)) & suffixFive;
                    strContainer.append(encodeTable.charAt(t));
                    // update
                    size = size - 5;
                    remain = remain & ((1 << size) - 1);
                }
            }
        }

        // hande remain
        if (size != 0) {
            int i = remain << (5 - size);
            strContainer.append(encodeTable.charAt(i));
        }

        // 处理base32的格式
        // 找到比他大的8的整数倍
        int total = NumUtil.findNextMultipleOfN(strContainer.length(), charNum);
        if (strContainer.length() < total) {
            strContainer.append(padding.toString().repeat(total - strContainer.length()));
        }
        return strContainer.toString();
    }


    /**
     * 编码long 类型
     *
     * @param num 原始数字
     * @return 编码之后的数字
     */
    public String encode(long num) {
        int i = 0;
        StringBuilder strContainer = new StringBuilder();
        while (i < charNum) {
            int b = (int) (num & ((1 << fiveBitUnit) - 1));
            strContainer.append(encodeTable.charAt(b));
            i++;
            num >>>= fiveBitUnit;
        }
        return strContainer.toString();
    }


    /**
     * 对base32的数字编码进行解码
     * @param encode 编码的数字
     * @return 原始数字
     */
    public long decodeForLong(String encode){
        char[] charArray = encode.toCharArray();
        long result = 0;

        for(int i = charArray.length - 1; i >= 0; i--){
            char c = charArray[i];
            int v = encodeTable.indexOf(c);

            result |= (long) (v & 0x1F) << (fiveBitUnit * i);
        }
        return result;
    }


    public byte[] decode(final String string) {
        return decode(StrUtils.getBytesUtf8(string));
    }

    public byte[] decode(final byte[] bytes) {

        List<Byte> container = new ArrayList<>();
        // 保留未处理的字节
        int size = 0;
        int remain = 0;
        for (byte a : bytes) {
            // 无符号
            int unsign = a & 0XFF;
            int b = encodeTable.indexOf((char) unsign);
            if (b == -1) break;

            if (size == 0) {
                size = 5;
                remain = b & ((1 << fiveBitUnit) - 1);
            } else {
                size += fiveBitUnit;
                remain = (remain << fiveBitUnit) | (b & ((1 << fiveBitUnit) - 1));

                if (size >= 8) {
                    byte b1 = (byte) (remain >>> (size - 8));
                    container.add(b1);
                    size = size - 8;
                    remain = remain & ((1 << size) - 1);
                }
            }
        }

        byte[] results = new byte[container.size()];
        for (int i = 0; i < container.size(); i++) {
            results[i] = container.get(i);
        }
        return results;
    }

    /**
     * 获取Base32  Alphabet
     *
     * @return 返回Base32的字符
     */
    public static String getBase32Alphabet() {
        StringBuilder cs = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            if (i <= 25) {
                cs.append((char) (i + 65));
                continue;
            }
            cs.append(2 + i - 26);
        }
        return cs.toString();
    }
}
